package com.yy.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
/**
 * @Author: chen
 * @Date: 2022/9/23 17:38
 * @Desc: redis可重入分布式锁
 */
public class RedisDistributeLock {

    private StringRedisTemplate redisTemplate;
    private String lockName;
    private String uuid;
    private long expire = 30;

    public RedisDistributeLock(StringRedisTemplate redisTemplate, String lockName, String uuid) {
        this.redisTemplate = redisTemplate;
        this.lockName = lockName;
        this.uuid = uuid + ":" + Thread.currentThread().getId();
    }


    public void lock() {
        lock(-1, TimeUnit.SECONDS);
    }

    /**
     * 加锁方法
     */
    public void lock(long time, TimeUnit unit) {
        if (time != -1) {
            expire = unit.toSeconds(time);
        }
        String script = "if redis.call('exists',KEYS[1])==0 or redis.call('hexists',KEYS[1],ARGV[1])==1 \n" +
                "then \n" +
                "    redis.call('hincrby',KEYS[1],ARGV[1],1) \n" +
                "    redis.call('expire',KEYS[1],ARGV[2]) \n" +
                "    return 1 \n" +
                "else \n" +
                "    return 0 \n" +
                "end";

        while (!redisTemplate.execute(RedisScript.of(script, Boolean.class),
                Collections.singletonList(lockName),
                uuid,
                String.valueOf(expire))
        ) {
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 加锁成功,返回之前,开启定时器自动续期
        renewExpire();
    }

    /**
     * 解锁方法
     */
    public void unlock() {
        String script = "if redis.call('hexists',KEYS[1],ARGV[1])==0\n" +
                "then\n" +
                "    return nil\n" +
                "elseif redis.call('hincrby',KEYS[1],ARGV[1],-1)==0\n" +
                "then\n" +
                "    return redis.call('del',KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        Long unlockFlag = redisTemplate.execute(
                RedisScript.of(script, Long.class),
                Collections.singletonList(lockName),
                uuid
        );

        if (null == unlockFlag) {
            throw new IllegalMonitorStateException("This lock doesn't belong to you");
        }
    }

    /**
     * 自动续期
     */
    private void renewExpire() {
        String script = "if redis.call('hexists',KEYS[1],ARGV[1])==1\n" +
                "then\n" +
                "    return redis.call('expire',KEYS[1],ARGV[2])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (redisTemplate.execute(RedisScript.of(script, Boolean.class),
                        Collections.singletonList(lockName),
                        uuid,
                        String.valueOf(expire))) {
                    renewExpire();
                }
            }
        }, expire * 1000 / 3);
    }
}
